/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2009, 2010.

	This file is part of Maxsi Engine.

	Maxsi Engine is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Engine is distributed in the hope that it will be useful, but WITHOUT
	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
	FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
	for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	MaxsiEngine.dll
	A general purpose C++ library for the Maxsi Technology Project.

	MaxsiBuffer.cpp
	A flexible buffer class.

******************************************************************************/

#include "MaxsiEngine.h"

BeginMaxsiNamespace
	
//=============================================================================
//	class MaxsiBuffer
//=============================================================================

MaxsiBuffer::MaxsiBuffer()
{
	Buffer			=	NULL;
	BufferSize		=	0;
	BufferUsed		=	0;
	BufferOffset	=	0;
	BufferIdealSize	=	BufferSize;
	BufferFlags	=	0;		

	ShouldDoEvenSillierDebugStuff		=	false;

	BufferLock		=	ThreadSystem()->CreateCriticalSection();
}

MaxsiBuffer::~MaxsiBuffer()
{
	if ( Buffer != NULL ) { delete[] Buffer; }

	ThreadSystem()->DeleteCriticalSection(BufferLock);
}

//=============================================================================
//	class MaxsiBuffer - Functions
//=============================================================================

//=============================================================================
//	MaxsiError MaxsiBuffer::MarkDataAsRead(BYTE* Pointer, size_t Size);
//
//	Called when data in the buffer is no longer used. This function resets
//	the data to the default value when in secure mode so that personal data
//	exists in the memory as little time as possible.
//=============================================================================
MaxsiError MaxsiBuffer::MarkDataAsRead(BYTE* Pointer, size_t Size)
{
	if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_SECURE )
	{
		memset(Pointer,0xCC,Size);
	}
	return MAXSI_ERROR_SUCCESS;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::Unlock();
//
//	Unlocks the buffer so that other threads can use it and revokes all rights
//	to the buffer from thr thread.
//=============================================================================
MaxsiError MaxsiBuffer::Unlock()
{
	SetBufferFlags(GetBufferFlags()	& ~MAXSI_BUFFER_FLAG_ALL_RIGHTS);
	
#ifdef _DEBUG
	//if ( BufferLock.RecursionCount == 1 )
	//{
		DebugCaller		=	NULL;
		DebugLine		=	NULL;
	//}
#endif

	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return MAXSI_ERROR_SUCCESS;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::Lock(FLAGSL Flags);
//
//	Locks the buffer so that only the current thread can acceess it with the
//	rights specified in the Flags parameter.
//=============================================================================
MaxsiError MaxsiBuffer::Lock(FLAGSL32 Flags
#ifdef _DEBUG
, char*		Caller
, size_t	Line
#endif
)
{
	ThreadSystem()->EnterCriticalSection(BufferLock);

#ifdef _DEBUG
	if ( DebugCaller == NULL && DebugLine == NULL )
	{
		DebugCaller		=	Caller;
		DebugLine		=	Line;
	}
#endif

	SetBufferFlags(GetBufferFlags() | Flags);

	return MAXSI_ERROR_SUCCESS;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::Write(BYTE* Input, size_t Size);
//
//	Writes data to the buffer if the thread has the correct rights.
//=============================================================================
MaxsiError MaxsiBuffer::Write(BYTE* Input, size_t Size)
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	// See if we may write to the buffer
	if ( !(GetBufferFlags() & MAXSI_BUFFER_FLAG_WRITE) )
	{
		Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	}
	else
	{
		// Do we have a buffer?
		if ( Buffer == NULL )
		{
			Result	=	MAXSI_ERROR_NOT_INITIALIZED;
		}
		else
		{
			// Is there room in the buffer?
			if ( BufferSize - BufferUsed < Size )
			{
				// Nope.
				Result			=	MAXSI_ERROR_OUT_OF_MEM;
			}
			else
			{
				// Yes, write to the buffer right away. Calculate where to size.

				size_t	Begin	=	(BufferOffset+BufferUsed) % BufferSize;

				if ( Begin+Size > BufferSize )
				{
					memcpy(Buffer+Begin,Input,BufferSize-Begin);
					memcpy(Buffer,Input+BufferSize-Begin,Size-(BufferSize-Begin));
				}
				else
				{
					memcpy(Buffer+Begin,Input,Size);
				}

				SetBufferUsed(BufferUsed+Size);
			}
		}
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::Write(MaxsiBuffer* Input, size_t Size);
//
//	Writes data to the buffer if the thread has the correct rights.
//=============================================================================
MaxsiError MaxsiBuffer::Write(MaxsiBuffer* Input, size_t Size)
{
	MAXSI_TODO_LOW("You forgot to implement MaxsiError MaxsiBuffer::Write(MaxsiBuffer* Input, size_t Size)!");
	return 0;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::Read(BYTE* Dest, size_t Size);
//
//	Reads data from the buffer if the thread has the correct rights.
//=============================================================================
MaxsiError MaxsiBuffer::Read(BYTE* Dest, size_t Size)
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	// See if we may read from the buffer
	if ( !(GetBufferFlags() & MAXSI_BUFFER_FLAG_READ) )
	{
		Result		=	MAXSI_ERROR_ACCESS_VIOLATION;
	}
	else
	{
		// Do we have a buffer?
		if ( Buffer == NULL )
		{
			Result	=	MAXSI_ERROR_NOT_INITIALIZED;
		}
		else
		{
			// Is there room in the buffer?
			if ( BufferUsed < Size )
			{
				// Nope.
				Result			=	MAXSI_ERROR_OUT_OF_MEM;
			}
			else
			{
				// Yes, read data from the buffer right away. Calculate where to size.

				if ( BufferOffset+Size > BufferSize )
				{
					memcpy(Dest,Buffer+BufferOffset,BufferSize-BufferOffset);
					memcpy(Dest+BufferSize-BufferOffset,Buffer,Size-(BufferSize-BufferOffset));					

					MarkDataAsRead(Buffer+BufferOffset,BufferSize-BufferOffset);
					MarkDataAsRead(Buffer,Size-(BufferSize-BufferOffset));
				}
				else
				{
					memcpy(Dest,Buffer+BufferOffset,Size);
					MarkDataAsRead(Buffer+BufferOffset,Size);
				}

				BufferOffset	+=	Size;
				BufferUsed		-=	Size;					
				BufferOffset	%=	BufferSize;

				if ( BufferSize != BufferIdealSize )
				{
					SetBufferSize(BufferIdealSize);
				}
			}
		}
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}


//=============================================================================
//	BYTE* MaxsiBuffer::GetBuffer();
//
//	Returns a pointer to the buffer if the thread has the correct rights,
//	NULL otherwise.
//=============================================================================
BYTE* MaxsiBuffer::GetBuffer()
{
	BYTE*	Result	=	NULL;

	ThreadSystem()->EnterCriticalSection(BufferLock);

	if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_POINTER )
	{
		Result		=	Buffer;
	}

	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	BYTE* MaxsiBuffer::GetPointer();
//
//	Returns a pointer to the current byte in the buffer if the thread has the
//	correct rights, NULL otherwise.
//=============================================================================
BYTE* MaxsiBuffer::GetPointer()
{
	BYTE*	Result	=	NULL;

	ThreadSystem()->EnterCriticalSection(BufferLock);

	if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_POINTER )
	{
		Result		=	Buffer+BufferOffset;
	}

	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	size_t MaxsiBuffer::GetBufferSize();
//
//	Returns the current buffer size.
//=============================================================================
size_t MaxsiBuffer::GetBufferSize()
{
	return BufferSize;
}

//=============================================================================
//	size_t MaxsiBuffer::GetBufferOffset();
//
//	Returns the buffer currently used.
//=============================================================================
size_t MaxsiBuffer::GetBufferUsed()
{
	return BufferUsed;
}

//=============================================================================
//	size_t MaxsiBuffer::GetBufferOffset();
//
//	Returns the current buffer offset.
//=============================================================================
size_t MaxsiBuffer::GetBufferOffset()
{
	return BufferOffset;
}

//=============================================================================
//	size_t MaxsiBuffer::GetBufferAvailable();
//
//	Returns the amount of unused space in this buffer
//=============================================================================
size_t MaxsiBuffer::GetBufferAvailable()
{
	return BufferSize-BufferUsed;
}


//=============================================================================
//	size_t MaxsiBuffer::GetPointerSizeFollowingData();
//
//	Returns the amount of bytes in a row following the current position in the
//	buffer.
//=============================================================================
size_t MaxsiBuffer::GetPointerSizeFollowingData()
{
	if ( BufferSize-BufferOffset > BufferUsed ) { return BufferUsed; }
	else return BufferSize-BufferOffset;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::SetBufferSize(size_t Size, bool Force);
//
//	Changes the size of the buffer if possible, and fails if not possible,
//	unless the Force parameter is set. If the size change is failed, it is
//	requested and will be done as soon as the buffer is small enough. If the
//	Force Parameter is set, the contents of the buffer after the operation
//	is undefined.
//=============================================================================
MaxsiError MaxsiBuffer::SetBufferSize(size_t Size, bool Force)
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	ThreadSystem()->EnterCriticalSection(BufferLock);

	if ( (Buffer && GetBufferFlags() & MAXSI_BUFFER_FLAG_POINTER) || !(GetBufferFlags() & MAXSI_BUFFER_FLAG_WRITE) )
	{
		Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
		BufferIdealSize	=	Size;
	}
	else
	{
		if ( Buffer == NULL )
		{
			Buffer		=	new	BYTE[Size];
			if ( Buffer == NULL )
			{
				Result			=	MAXSI_ERROR_MEM_ALLOC_FAIL;
				BufferIdealSize	=	Size;
			}
			else
			{
				BufferUsed		=	0;
				BufferOffset	=	0;
				BufferSize		=	Size;
				BufferIdealSize	=	BufferSize;
			}
		}
		else
		{
			if ( BufferUsed > Size && Force == false )
			{
				Result			=	MAXSI_ERROR_OUT_OF_MEM;
				BufferIdealSize	=	Size;
			}
			else
			{
				BYTE*	NewBuffer	=	new	BYTE[Size];
				if ( NewBuffer == NULL )
				{
					Result			=	MAXSI_ERROR_MEM_ALLOC_FAIL;
					BufferIdealSize	=	Size;
				}
				else
				{
					if ( BufferUsed > Size )
					{
						// Warning: We changed the size of the buffer to something smaller and forced it
						// to be done right away, leaving the contents of the new buffer undefined.
					}
					else
					{
						if ( BufferOffset+BufferUsed > BufferSize )
						{
							memcpy(NewBuffer,Buffer+BufferOffset,BufferSize-BufferOffset);
							memcpy(NewBuffer+BufferSize-BufferOffset,Buffer,BufferUsed-(BufferSize-BufferOffset));
						}
						else
						{
							memcpy(NewBuffer,Buffer,BufferUsed);
						}
						MarkDataAsRead(Buffer,BufferSize);
						delete[]	Buffer;
						Buffer			=	NewBuffer;
						BufferOffset	=	0;
						BufferSize		=	Size;
						BufferIdealSize	=	BufferSize;
					}
				}
			}

		}
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::SetBufferOffset(size_t Offset);
//
//	Changes the buffer offset if the thread has the correct access rights.
//=============================================================================
MaxsiError MaxsiBuffer::SetBufferOffset(size_t Offset)
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	ThreadSystem()->EnterCriticalSection(BufferLock);

	if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_POINTER && GetBufferFlags() & MAXSI_BUFFER_FLAG_WRITE )
	{
		BufferOffset	=	Offset;
	}
	else
	{
		Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::SetBufferUsed(size_t Used);
//
//	Changes the buffer used if the thread has the correct access rights.
//=============================================================================
MaxsiError MaxsiBuffer::SetBufferUsed(size_t Used)
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	ThreadSystem()->EnterCriticalSection(BufferLock);

	if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_POINTER && GetBufferFlags() & MAXSI_BUFFER_FLAG_WRITE )
	{
		if ( Used > BufferSize )
		{
			MAXSI_UNDEFINED_BEHAVIOR("Attempted to set buffer usage higher than the buffer's size! This is most likely caused by a failure in the program's logic.");
		}
		BufferUsed		=	Used;
	}
	else
	{
		Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	BYTE* MaxsiBuffer::ProgressPointer(BYTE* Position, size_t Size);
//
//	Returns the same pointer moved Size bytes further into the buffer.
//	Returns NULL if the thread doesn't have pointer access, or if the pointer
//	provided is not in the buffer
//=============================================================================
BYTE* MaxsiBuffer::ProgressPointer(BYTE* Position, size_t Size)
{
	BYTE*	Result	=	NULL;

	ThreadSystem()->EnterCriticalSection(BufferLock);

	if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_POINTER &&
		(size_t) Position >= (size_t)Buffer &&
		(size_t) Position <= (size_t)Buffer+BufferSize) 
	{
		Result	=	Buffer+((((size_t)Position-(size_t)Buffer)+Size)%BufferSize);
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::ProgressBuffer(size_t Size);
//
//	Progresses the buffer Size bytes forward and returns an error if it goes
//	beyond the size of the buffer. If the Force parameter is set the buffer is
//	progressed no matter what, but the contents are undefined.
//=============================================================================
MaxsiError MaxsiBuffer::ProgressBuffer(size_t Size, bool Force)
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;
	
	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	// See if we may read from the buffer
	if ( !(GetBufferFlags() & MAXSI_BUFFER_FLAG_READ) )
	{
		Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	}
	else
	{
		if ( BufferUsed < Size )
		{
			if ( Force == false )
			{
				Result	=	MAXSI_ERROR_OUT_OF_MEM;
			}
			else
			{
				// Contents are undefined.
				BufferUsed		=	0;
				BufferOffset	+=	Size;
				BufferOffset	%=	BufferSize;
			}
		}
		else
		{	
			if ( BufferOffset+Size > BufferSize )
			{	
				MarkDataAsRead(Buffer+BufferOffset,BufferSize-BufferOffset);
				MarkDataAsRead(Buffer,Size-(BufferSize-BufferOffset));
			}
			else
			{
				MarkDataAsRead(Buffer+BufferOffset,Size);
			}

			BufferUsed		-=	Size;
			BufferOffset	+=	Size;
			BufferOffset	%=	BufferSize;
		}
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::Clear();
//
//	Marks all data in the buffer as read and resets the pointer to the first
//	byte in the buffer.
//=============================================================================
MaxsiError MaxsiBuffer::Clear()
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;
	
	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	// See if we may read from the buffer
	if ( !(GetBufferFlags() & MAXSI_BUFFER_FLAG_READ) )
	{
		Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	}
	else
	{
		if ( BufferOffset+BufferUsed > BufferSize )
		{	
			MarkDataAsRead(Buffer+BufferOffset,BufferSize-BufferOffset);
			MarkDataAsRead(Buffer,BufferUsed-(BufferSize-BufferOffset));
		}
		else
		{
			MarkDataAsRead(Buffer+BufferOffset,BufferUsed);
		}

		BufferUsed		=	0;
		BufferOffset	=	0;
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

//=============================================================================
//	MaxsiError MaxsiBuffer::Clear();
//
//	Marks all data in the buffer as read and resets the pointer to the first
//	byte in the buffer.
//=============================================================================
MaxsiError MaxsiBuffer::SetInUse()
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;
	
	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	// See if we may read from the buffer
	//if ( !(GetBufferFlags() & MAXSI_BUFFER_FLAG_POINTER) )
	//{
	//	Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	//}
	//else
	//{
		SetBufferFlags(GetBufferFlags() | MAXSI_BUFFER_FLAG_INUSE);
	//}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return	Result;
}

MaxsiError MaxsiBuffer::ClearInUse()
{
	MaxsiError Result	=	MAXSI_ERROR_SUCCESS;
	
	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	//// See if we may read from the buffer
	//if ( !(GetBufferFlags() & MAXSI_BUFFER_FLAG_POINTER) )
	//{
	//	Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	//}
	//else
	//{
		SetBufferFlags(GetBufferFlags()	&	~MAXSI_BUFFER_FLAG_INUSE);
	//}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}


FLAGSL32 MaxsiBuffer::GetBufferFlags()
{
	return BufferFlags;
}

FLAGSL32 MaxsiBuffer::SetBufferFlags(FLAGSL32 NewFlags)
{
	BufferFlags		=	NewFlags;
	return NewFlags;
}

bool MaxsiBuffer::IsInUse()
{	
	bool	Result	=	false;
	
	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	// See if we may read from the buffer
	if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_INUSE )
	{
		Result			=	true;
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

MaxsiError MaxsiBuffer::MayWrite()
{		
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;
	
	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	// See if we may read from the buffer
	if ( !(GetBufferFlags() & MAXSI_BUFFER_FLAG_WRITE) )
	{
		Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	}
	else if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_INUSE )
	{
		Result			=	MAXSI_ERROR_IS_IN_USE;
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}

MaxsiError MaxsiBuffer::MayRead()
{		
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;
	
	ThreadSystem()->EnterCriticalSection(BufferLock);
	
	// See if we may read from the buffer
	if ( !(GetBufferFlags() & MAXSI_BUFFER_FLAG_READ) )
	{
		Result			=	MAXSI_ERROR_ACCESS_VIOLATION;
	}
	else if ( GetBufferFlags() & MAXSI_BUFFER_FLAG_INUSE )
	{
		Result			=	MAXSI_ERROR_IS_IN_USE;
	}
	
	ThreadSystem()->LeaveCriticalSection(BufferLock);

	return Result;
}


MaxsiBuffer* CreateBuffer(size_t Size, FLAGSL32 flags)
{
	MaxsiBuffer*	Result	=	new	MaxsiBuffer;
	if ( Result == NULL ) { return NULL; }
	
	Result->Lock(MAXSI_BUFFER_FLAG_ALL_RIGHTS MAXSI_BUFFER_LOCK_PARAMSEX);
	Result->SetBufferSize(Size);
	Result->Unlock();

	return Result;
}

bool DeleteBuffer(MaxsiBuffer* Buffer)
{
	delete Buffer;
	return true;
}

size_t FindStringInBuffer(MaxsiBuffer* Buffer, char* String2)
{
	return FindStringInBufferEx(Buffer,(BYTE*)String2,strlen(String2));
}

size_t FindStringInBufferEx(MaxsiBuffer* Buffer, BYTE* String2, size_t String2Len)
{
	if ( Buffer->MayRead() !=	MAXSI_ERROR_SUCCESS )
	{
		return SIZE_MAX;
	}

	BYTE*	Pointer		=	(BYTE*)Buffer->GetBuffer();

	if ( Pointer ==	NULL )
	{
		return SIZE_MAX;
	}

	size_t	Row		=	Buffer->GetPointerSizeFollowingData();
	size_t	Used	=	Buffer->GetBufferUsed();
	size_t	Size	=	Buffer->GetBufferSize();
	size_t	Offset	=	Buffer->GetBufferOffset();

	if ( Row >= Used )
	{
		return	FindStringEx((MESTR*)(Pointer+Offset),Used,(MESTR*)String2,String2Len);
	}
	else
	{
		// This is a bit more tricky since the buffer wraps

		size_t X = 0, Y = 0;

		Used	-=	String2Len;

		// Loop through X and check at each X if Y follows, if so return X
		for (X = 0; X < Used; X++)
		{
			for (Y = 0; Y < String2Len; Y++)
			{
				if ( Pointer[(Offset+Y)%Size] != String2[Y] )
				{
					break; // Nope.
				}
			}
			if ( Y == String2Len )
			{
				return X; // Found at location X
			}
		}
	}		
	
	return SIZE_MAX; // Not Found!
}

EndMaxsiNamespace

